MediaInfoをAWS Lambda (Python)で使って動画ファイルのメタ情報を取得してみた
はじめに
清水です。動画やオーディオファイルの各種情報を取得できるツールMediaInfoをAWS Lambda内で使ってみました。必要となるMediaInfoの実行ファイルの作成から簡単なLambda関数内での動作確認までをまとめてみたいと思います。
なお、Lambdaの環境(ランタイム)はPython(Python 3.7
)となります。
MediaInfo実行ファイルのコンパイル
まずはLambda関数内で使用するための実行ファイルを、MediaInfoのソースコードファイルからコンパイルして作成します。以下のAWS Lambda 開発者ガイド「AWS Lambda ランタイム」の項目を確認し、Python 3.7ランタイムではオペレーティングシステムはAmazon Linux(Amazon Linux 2ではなく、1のほう)とのことでした。
EC2でAmazon Linuxのインスタンスを起動してコンパイルを行います。上記「AWS Lambda ランタイム」ページに記載されていた以下のAMIを使用しました(リージョンはap-northeast-1となります)。コンパイルすることもありインスタンスタイプはt2.largeを選択します。
- AMI Name
- amzn-ami-hvm-2018.03.0.20181129-x86_64-gp2
- AMI ID
- ami-00a5245b4816c38e6
- Description
- Amazon Linux AMI 2018.03.0.20181129 x86_64 HVM gp2
コンパイル手順はこちらのサイトを参考にしました。
事前に'Development Tools'
とlibcurl-devel
をyumでインストールします。(前者はgroupinstallします。)'Development Tools'
についてはコンパイルに必要な開発ツールですね。後述しますがLambdaでのMediaInfo実行にはcurlオプションが必須になるかと思いますので、libcur-devel
もインストールしておきます。
$ sudo yum groupinstall 'Development tools' $ sudo yum install libcurl-devel
続いてMediaInfoのソースファイルをダウンロード、付属のスクリプトでコンパイルを行います。今回、MediaInfoのバージョンは最新版の19.09を使用しました。
ダウンロードしたソースファイルを展開、作成されたディレクトリに移動してコンパイル用のスクリプトCLI_Compile.sh
を実行します。ここでもオプションで--with-libcur
を付与しておきます。
実際に実行したコマンドは下記になります。(CLI_Compile.sh
実行についてはログをファイルにも書き出すようにしました。)
$ wget https://mediaarea.net/download/binary/mediainfo/19.09/MediaInfo_CLI_19.09_GNU_FromSource.tar.xz $ tar xvf MediaInfo_CLI_19.09_GNU_FromSource.tar.xz $ cd MediaInfo_CLI_GNU_FromSource $ ./CLI_Compile.sh --with-libcurl 2>&1 | tee CLI_Compile.log
コンパイルには2分ちょっとかかりました。無事にコンパイルできると、以下のメッセージが表示されます。
MediaInfo (CLI) compiled MediaInfo executable is MediaInfo/Project/GNU/CLI/mediainfo For installing, cd MediaInfo/Project/GNU/CLI && make install
今回はコンパイルしたマシンにインストールすることはしませんので、make install
コマンドは実行しません。ただ後ほどLayer機能を使ってこのMediaInfoの実行ファイルを使用します。binディレクトリを作成して実行ファイルをmediainfo
をこのディレクトリにコピー、binディレクトリ配下をまとめて1つのzipファイルにまとめておきます。
$ mkdir bin $ cp -p MediaInfo_CLI_GNU_FromSource/MediaInfo/Project/GNU/CLI/mediainfo ./bin/ $ zip mediainfo_v19.09.zip ./bin/*
zipファイルを展開すると以下のような構成になっています。
$ unzip mediainfo_v19.09.zip Archive: mediainfo_v19.09.zip inflating: bin/mediainfo $ ls -R .: bin mediainfo_v19.09.zip ./bin: mediainfo
Lambda関数の作成
コンパイルしたMediaInfo実行ファイルをLambda関数内で使えるように設定していきます。今回動作確認として、S3バケットへのファイルアップロード(オブジェクトの新規作成)でLambda関数をキック、アップロードされたファイルからMediaInfoで情報を取り出し、解像度情報についてログ(CloudWatch Logs)に書き出す、というコードを準備しました。コード自体は次章にまとめるとして、本章ではまずLambdaで使うIAMロール、MediaInfoのLayer、そしてLambda関数自体の設定についてまとめます。
Lambda関数で利用するIAMロールの作成
まずはLambda関数にアタッチするIAMロールを作成します。権限はAWS manged policyのAWSLambdaBasicExecutionRole
の他、S3バケットにアップロードされたファイルへのアクセス権も付与しておきます。(今回は大雑把にAmazonS3FullAccess
のAWS managed policyを使用しました。実際の環境にあわせて適切な権限を選択しましょう。)
AWSマネジメントコンソールのIAM、Roleの画面の[Create role]から進めます。ユースケースとしてLambdaを選択しましょう。
ポリシー選択画面ではAWS managed policyのAmazonS3FullAccess
、AWSLambdaBasicExecutionRole
を選択します。今回はRole名をMediaInfo-on-Lambda-Role
としました。[Create role]でIAMロールを作成します。
MediaInfoのLayerの作成
続いてLambda関数で使用する、MediaInfoのLayerを作成します。MediaInfoの実行ファイルをソースコードファイルとまとめてzipにしLambda関数に設定することもできますが、zipファイル(主にMediaInfo実行ファイル)の容量が大きくなり、マネジメントコンソール上でのソースコードの確認や編集ができなくなります。またLayerを作成しておけば、複数のLambda関数からMediaInfo実行ファイルを扱える、というメリットもあります。
AWSマネジメントコンソールのLambdaのページ、Layersの[Create layer]から進みます。
NameはそのままMediaInfo
とし、DescriptionでMediaInfoの詳細バージョン(19.09)とコンパイル環境(Amazon Linux)を記載しておきました。今回はUpload a .zip file
を選択し、先ほどEC2で作成したzipファイルmediainfo_v19.09.zip
をいちどローカル環境にコピー後してアップロードしました。Comatible runtimesとしてはPython 3.7
を選択し、[Create]をクリックします。
Layerが作成できました。(作っては消しての試行錯誤の結果、version 6で作成されています。初回作成時であればversion 1となるはずです。)
Lambda関数の設定
IAMロールとLayerが作成できたら、続いてLambda関数本体の作成を進めていきます。AWSマネジメントコンソールのLambdaのページから、[Create function]で関数を作成します。Author from scratchを選択して、関数名、ランタイムを入力、IAMロールを選択します。関数名はMediaInfo-on-Lambda
としましました。ランタイムはPython 3.7
です。IAMロールは先ほど作成したMediaInfo-on-Lambda-Role
を選択します。
Lambda関数作成後、遷移したページでまずはPythonのコードを設定します。Function codeの箇所、デフォルトの内容を次章に示すコードでまるっと上書きしてしまいます。
続いてBasic settingsの項目を編集します。デフォルトではメモリが128MB、タイムアウトは3秒ですが、メモリを256MB
、そしてタイムアウトを1分
と設定しました。特にタイムアウトについては設定必須かと思います。デフォルトの3秒の場合、S3からのファイル取得中に3秒以上経過し実際にタイムアウトが発生してしまうことがありました。
そしてLayer機能についての設定です。DesignerのLayers部分をクリック、Layersの項目の[Add a layer]で進みます。
先ほど作成したLayerMediaInfo
を選択、バージョンもLayerにあったものを選択して[Add]で追加します。
Layerが追加されました。ここまで設定したら右上の[Save]ボタンでLambda関数の内容を保存しておきましょう。
S3からLambdaをトリガする設定
S3にファイルアップロード(オブジェクトの新規作成)があったら、Lambdaを実行するようにします。Lambdaのマネジメントコンソールから行う方法もありますが、今回はS3のマネジメントコンソールから設定しました。
対象のS3バケット、Properties項目からAdvanced settingsのEventsを設定します。Add notificationをクリック、現れた設定項目でNameを「Execute-MediaInfo-on-Lambda」と入力、EventはAll object create eventsで全てのオブジェクト作成イベント時に実行されるようにしました。Send toでLambda Function
を選択、Lambdaの部分でLambda関数MediaInfo-on-Lambda
を選んで[Save]します。
なお、設定後にLambdaのマネジメントコンソール、Designerの箇所を確認すると、以下のようにtriggerが登録されていることがわかります。
Lambda関数内でMediaInfoを実行
以上設定ができたら、実際にLambda関数をキックしてMediaInfoを実行してみます。Lambdaをトリガするよ設定したS3バケットに動画ファイル(ファイル名IMG_8947.MOV
)をアップロードしました。CloudWach LogsでLambda関数実行のログを確認してみます。
ファイルサイズ、横縦の解像度情報が取得できていますね!
Lambda関数で実行するコード
今回Lambda関数に設定したPythonのコードが下記になります。(なお筆者はあまりコードを書かなかったり、だいぶ久しぶりに書いたコードだったりしますので拙い内容であることはご了承ください。参考に挙げているサイトをはじめいろいろなページ、既出のコードを参考にしました。)
今回、Lambda関数内でMediaInfoを実行します。通常MediaInfoを実行する場合は、MediaInfo実行ファイルがあるシステム上に、解析対象の動画ファイルがあるかと思います。(手元のMacにある動画ファイルをMac上のMediaInfoで解析する、など。)Lambdaで同様に動画ファイルをいちどローカルストレージに保存して、ということもできるかと思いますが、/tmp
ディレクトリに保存してと考えるとファイルサイズの上限が512MB
となってしまいます。そのためMediaInfoには動画ファイルのURLを渡して、動画ファイルのローカルへのダウンロードなしに解析を行います。このMediaInfoでURLサポート機能を追加するため、コンパイル時に--with-libcurl
オプションを付与していました。このMediaInfoの引数にURLを指定しての実行が、43、44行目になります。
MediaInfoの引数として渡すURLですが、対象ファイルがS3上のオブジェクトであり、Lambdaからアクセスするために署名付きURLとしています。(変数signed_url
)処理内容としては14-20行目の関数get_signed_url
で行っています。
MediaInfoの実行結果はJSON形式で出力し、このデータ内からファイルサイズ、Width、Heightを別途用意した変数video_info
に格納、70行目の少し整形をしてログに書き出す、ということをしています。
import json import logging import os import subprocess import urllib.parse import boto3 SIGNED_URL_EXPIRATION = 300 logger = logging.getLogger('boto3') logger.setLevel(logging.INFO) def get_signed_url(expires_in, bucket, obj): s3_cli = boto3.client("s3") presigned_url = s3_cli.generate_presigned_url( 'get_object', Params={'Bucket': bucket, 'Key': obj}, ExpiresIn=expires_in) return presigned_url s3 = boto3.client('s3') logger.info("Loading function") def lambda_handler(event, context): logger.info(json.dumps(event)) for s3_record in event['Records']: try: logger.info("Working on new s3_record...") key = urllib.parse.unquote_plus(s3_record['s3']['object']['key'], encoding='utf-8') bucket = s3_record['s3']['bucket']['name'] logger.info("Bucket: {} \t Key: {}".format(bucket, key)) signed_url = get_signed_url(SIGNED_URL_EXPIRATION, bucket, key) logger.info("Signed URL: {}".format(signed_url)) # MediaInfoを実行 json_output = subprocess.check_output( ["mediainfo", "--full", "--output=JSON", signed_url]) logger.info("MediaInfo Output: {}".format(json_output)) # MediaInfoの実行結果からFileSize, Width, Heightを取り出す json_data = json.loads(json_output) tracks=json_data['media']['track'] video_info = {} for track in tracks: if track['@type'] == "General": if 'GeneralFileSize' not in video_info: video_info['GeneralFileSize'] = track.get('FileSize') else: logger.info('MediaInfo num of General > 1') elif track['@type'] == "Video": if 'VideoHeight' not in video_info: video_info['VideoHeight'] = track.get('Height') else: logger.info('MediaInfo number of Video > 1') if 'VideoWidth' not in video_info: video_info['VideoWidth'] = track.get('Width') else: logger.info('MediaInfo number of Video Width > 1') logger.info("video_info: {}".format(video_info)) # ファイル名とFileSize, Width, Heightをログ出力 input_file_name = key.rsplit('/', 1)[-1] logger.info("\n{} Infomation => FileSize: {} Byte, Width: {} pixel, Height: {} pixel".format( input_file_name, video_info['GeneralFileSize'], video_info['VideoWidth'], video_info['VideoHeight'])) except Exception as e: print(e) print('Error getting object {} from bucket {}. Make sure they exist and your bucket is in the same region as this function.'.format(key, bucket)) raise e
まとめ
Lambda関数内で動画解析ツールのMediaInfoを実行してみました。本エントリではLambda内でファイルサイズ、Width、Heightの情報を抜き出してログに出力する、というところまででしたが、Lambda内で動画ファイル情報が解析できるといろいろと用途が広がると思います。例えば以下のようなS3バケットにファイルをアップロードして、LambdaでMediaConvertのジョブをキックするような処理です。動画の解像度情報がなどLambda関数内でわかれば、それにあわせたMediaConvertのJob Templateを選択してジョブを作成する、ということができますね。これで入力元動画にあわせたABR(Adaptive bitrate)での動画出力、なども可能です。
またそのほか動画ファイルのメタ情報(長さなど)を例えばDynamoDBなどに保存しておき、アプリケーションと連携させる、なども考えられると思います。MediaInfo自体とてもパワフルなツールだと思いますが、これがLambda関数内で使えるのは嬉しいですね!
参考
- Extracting Video Metadata using Lambda and Mediainfo | AWS Compute Blog
- Running MediaInfo as an AWS Lambda Function | AWS Media Blog
- GitHub - awslabs/video-on-demand-on-aws: An automated reference implementation leveraging AWS Step Functions and AWS Media Services to deploy a scalable fault tolerant Video on demand workflow